﻿
using Xacml.Core;
using Xacml.Core.Context;
using Xacml.Core.Interfaces;
using Xacml.Core.Policy;
using System.Collections;

namespace Xacml.Core.Runtime
{
  public abstract class ApplyBase : IEvaluable
  {
    private ApplyBaseReadWrite _applyBase;

    public ApplyBaseReadWrite ApplyDefinition
    {
      get
      {
        return this._applyBase;
      }
    }

    protected ApplyBase(ApplyBaseReadWrite apply)
    {
      this._applyBase = apply;
    }

    public virtual EvaluationValue Evaluate(EvaluationContext context)
    {
      context.Trace("Calling function: {0}", (object) this._applyBase.FunctionId);
      IFunction function = context.Engine.GetFunction(this._applyBase.FunctionId);
      if (function == null)
      {
        context.Trace("ERR: function not found {0}", (object) this._applyBase.FunctionId);
        context.ProcessingError = true;
        return EvaluationValue.Indeterminate;
      }
      IFunctionParameterCollection parameterCollection = this.ProcessArguments(context, new IExpressionCollection(this._applyBase.Arguments));
      return context.Engine.EvaluateFunction(context, function, parameterCollection.ToArray());
    }

    private IFunctionParameterCollection ProcessArguments(EvaluationContext context, IExpressionCollection arguments)
    {
      context.Trace("Processing arguments");
      context.AddIndent();
      IFunctionParameterCollection parameterCollection = new IFunctionParameterCollection();
      foreach (IExpression expression in (CollectionBase) arguments)
      {
        if (expression is ApplyElement)
        {
          context.Trace("Nested apply");
          EvaluationValue evaluationValue = new Apply((ApplyElement) expression).Evaluate(context);
          context.TraceContextValues();
          if (!evaluationValue.IsIndeterminate)
          {
            if (!context.IsMissingAttribute)
              parameterCollection.Add((IFunctionParameter) evaluationValue);
          }
          else
            parameterCollection.Add((IFunctionParameter) evaluationValue);
        }
        else if (expression is FunctionElementReadWrite)
        {
          FunctionElement functionElement = new FunctionElement(((FunctionElementReadWrite) expression).FunctionId, ((XacmlElement) expression).SchemaVersion);
          context.Trace("Function {0}", (object) functionElement.FunctionId);
          IFunction function = context.Engine.GetFunction(functionElement.FunctionId);
          if (function == null)
          {
            context.Trace("ERR: function not found {0}", (object) this._applyBase.FunctionId);
            context.ProcessingError = true;
            parameterCollection.Add((IFunctionParameter) EvaluationValue.Indeterminate);
          }
          else
            parameterCollection.Add((IFunctionParameter) function);
        }
        else if (expression is VariableReferenceElement)
        {
          VariableReferenceElement referenceElement = expression as VariableReferenceElement;
          VariableDefinition variableDefinition = context.CurrentPolicy.VariableDefinition[(object) referenceElement.VariableId] as VariableDefinition;
          context.TraceContextValues();
          if (!variableDefinition.IsEvaluated)
            parameterCollection.Add((IFunctionParameter) variableDefinition.Evaluate(context));
          else
            parameterCollection.Add((IFunctionParameter) variableDefinition.Value);
        }
        else if (expression is Xacml.Core.Policy.AttributeValueElementReadWrite)
        {
          context.Trace("Attribute value {0}", (object) expression.ToString());
          parameterCollection.Add((IFunctionParameter) new Xacml.Core.Policy.AttributeValueElement(((Xacml.Core.Policy.AttributeValueElementReadWrite) expression).DataType, ((Xacml.Core.Policy.AttributeValueElementReadWrite) expression).Contents, ((XacmlElement) expression).SchemaVersion));
        }
        else if (expression is AttributeDesignatorBase)
        {
          context.Trace("Processing attribute designator: {0}", (object) expression.ToString());
          AttributeDesignatorBase attributeDesignatorBase = (AttributeDesignatorBase) expression;
          BagValue bagValue = context.Engine.Resolve(context, attributeDesignatorBase);
          if (bagValue.BagSize == 0)
          {
            if (expression is SubjectAttributeDesignatorElement)
            {
              foreach (Xacml.Core.Context.SubjectElement subjectElement in (CollectionBase) context.ContextDocument.Request.Subjects)
              {
                AttributeElement attribute = context.Engine.GetAttribute(context, attributeDesignatorBase, (Xacml.Core.Context.TargetItemBase) subjectElement);
                if (attribute != null)
                {
                  context.Trace("Adding subject attribute designator: {0}", (object) attribute.ToString());
                  bagValue.Add((object) attribute);
                  break;
                }
              }
            }
            else if (expression is ResourceAttributeDesignatorElement)
            {
              AttributeElement attribute = context.Engine.GetAttribute(context, attributeDesignatorBase, (Xacml.Core.Context.TargetItemBase) context.CurrentResource);
              if (attribute != null)
              {
                context.Trace("Adding resource attribute designator {0}", (object) attribute.ToString());
                bagValue.Add((object) attribute);
              }
            }
            else if (expression is ActionAttributeDesignatorElement)
            {
              AttributeElement attribute = context.Engine.GetAttribute(context, attributeDesignatorBase, (Xacml.Core.Context.TargetItemBase) context.ContextDocument.Request.Action);
              if (attribute != null)
              {
                context.Trace("Adding action attribute designator {0}", (object) attribute.ToString());
                bagValue.Add((object) attribute);
              }
            }
            else if (expression is EnvironmentAttributeDesignatorElement)
            {
              AttributeElement attribute = context.Engine.GetAttribute(context, attributeDesignatorBase, (Xacml.Core.Context.TargetItemBase) context.ContextDocument.Request.Environment);
              if (attribute != null)
              {
                context.Trace("Adding envrironment attribute designator {0}", (object) attribute.ToString());
                bagValue.Add((object) attribute);
              }
            }
          }
          if (bagValue.BagSize == 0 && attributeDesignatorBase.MustBePresent)
          {
            context.Trace("Attribute is missing");
            context.IsMissingAttribute = true;
          }
          else
            parameterCollection.Add((IFunctionParameter) bagValue);
        }
        else if (expression is AttributeSelectorElement)
        {
          context.Trace("Attribute selector");
          try
          {
            BagValue bagValue = context.Engine.Resolve(context, (AttributeSelectorElement) expression);
            if (bagValue.Elements.Count == 0 && ((AttributeReferenceBase) expression).MustBePresent)
            {
              context.Trace("Attribute is missing");
              context.IsMissingAttribute = true;
            }
            else
              parameterCollection.Add((IFunctionParameter) bagValue);
          }
          catch (EvaluationException ex)
          {
            context.Trace("ERR: {0}", (object) ex.Message);
            parameterCollection.Add((IFunctionParameter) EvaluationValue.Indeterminate);
            context.ProcessingError = true;
          }
        }
      }
      context.RemoveIndent();
      return parameterCollection;
    }
  }
}
